/*
* Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: James Devenish
*/
package org.h2.test.jdbcx;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Random;
import javax.sql.XAConnection;
import javax.sql.XADataSource;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import org.h2.jdbcx.JdbcDataSource;
import org.h2.test.TestBase;
import org.h2.util.JdbcUtils;
/**
* Basic XA tests.
*/
public class TestXA extends TestBase {
private static final String DB_NAME1 = "xadb1";
private static final String DB_NAME2 = "xadb2";
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
@Override
public void test() throws Exception {
testRollbackWithoutPrepare();
testXAAutoCommit();
deleteDb("xa");
testMixedXaNormal();
testXA(true);
deleteDb(DB_NAME1);
deleteDb(DB_NAME2);
testXA(false);
deleteDb("xa");
deleteDb(DB_NAME1);
deleteDb(DB_NAME2);
}
private void testRollbackWithoutPrepare() throws Exception {
if (config.memory) {
return;
}
Xid xid = new Xid() {
@Override
public int getFormatId() {
return 3145;
}
@Override
public byte[] getGlobalTransactionId() {
return new byte[] { 1, 2, 3, 4, 5, 6, 6, 7, 8 };
}
@Override
public byte[] getBranchQualifier() {
return new byte[] { 34, 43, 33, 3, 3, 3, 33, 33, 3 };
}
};
deleteDb("xa");
JdbcDataSource ds = new JdbcDataSource();
ds.setURL(getURL("xa", true));
ds.setPassword(getPassword());
Connection dm = ds.getConnection();
Statement stat = dm.createStatement();
stat.execute("CREATE TABLE IF NOT EXISTS TEST(ID INT PRIMARY KEY, VAL INT)");
stat.execute("INSERT INTO TEST(ID,VAL) VALUES (1,1)");
dm.close();
XAConnection c = ds.getXAConnection();
XAResource xa = c.getXAResource();
Connection connection = c.getConnection();
xa.start(xid, XAResource.TMJOIN);
PreparedStatement ps = connection.prepareStatement(
"UPDATE TEST SET VAL=? WHERE ID=?");
ps.setInt(1, new Random().nextInt());
ps.setInt(2, 1);
ps.close();
xa.rollback(xid);
connection.close();
c.close();
deleteDb("xa");
}
private void testMixedXaNormal() throws Exception {
JdbcDataSource ds = new JdbcDataSource();
ds.setURL("jdbc:h2:mem:test");
ds.setUser("sa");
ds.setPassword("");
XAConnection xa = ds.getXAConnection();
Connection c = xa.getConnection();
assertTrue(c.getAutoCommit());
MyXid xid = new MyXid();
XAResource res = xa.getXAResource();
res.start(xid, XAResource.TMNOFLAGS);
assertTrue(!c.getAutoCommit());
res.end(xid, XAResource.TMSUCCESS);
res.commit(xid, true);
assertTrue(c.getAutoCommit());
res.start(xid, XAResource.TMNOFLAGS);
assertTrue(!c.getAutoCommit());
res.end(xid, XAResource.TMFAIL);
res.rollback(xid);
assertTrue(c.getAutoCommit());
c.close();
xa.close();
}
/**
* A simple Xid implementation.
*/
public static class MyXid implements Xid {
private final byte[] branchQualifier = { 0 };
private final byte[] globalTransactionId = { 0 };
@Override
public byte[] getBranchQualifier() {
return branchQualifier;
}
@Override
public int getFormatId() {
return 0;
}
@Override
public byte[] getGlobalTransactionId() {
return globalTransactionId;
}
}
private void testXAAutoCommit() throws Exception {
JdbcDataSource ds = new JdbcDataSource();
ds.setURL("jdbc:h2:mem:test");
ds.setUser("sa");
ds.setPassword("");
XAConnection xa = ds.getXAConnection();
MyXid xid = new MyXid();
xa.getXAResource().start(xid,
XAResource.TMNOFLAGS);
Connection c = xa.getConnection();
assertTrue(!c.getAutoCommit());
c.close();
xa.close();
}
private void testXA(boolean useOneDatabase) throws SQLException {
String url1 = getURL(DB_NAME1, true);
String url2 = getURL(DB_NAME2, true);
XAConnection xaConn1 = null;
XAConnection xaConn2 = null;
Connection conn1 = null;
Connection conn2 = null;
Statement stat1 = null;
Statement stat2 = null;
try {
trace("xads1 = createXADatasource1()");
XADataSource xaDs1 = createXADatasource(useOneDatabase, url1);
trace("xads2 = createXADatasource2()");
XADataSource xaDs2 = createXADatasource(useOneDatabase, url2);
trace("xacon1 = xads1.getXAConnection()");
xaConn1 = xaDs1.getXAConnection();
trace("xacon2 = xads2.getXAConnection()");
xaConn2 = xaDs2.getXAConnection();
trace("xares1 = xacon1.getXAResource()");
XAResource xares1 = xaConn1.getXAResource();
trace("xares2 = xacon2.getXAResource()");
XAResource xares2 = xaConn2.getXAResource();
trace("xares1.recover(XAResource.TMSTARTRSCAN)");
Xid[] xids1 = xares1.recover(XAResource.TMSTARTRSCAN);
if ((xids1 == null) || (xids1.length == 0)) {
trace("xares1.recover(XAResource.TMSTARTRSCAN): 0");
} else {
trace("xares1.recover(XAResource.TMSTARTRSCAN): " + xids1.length);
}
trace("xares2.recover(XAResource.TMSTARTRSCAN)");
Xid[] xids2 = xares2.recover(XAResource.TMSTARTRSCAN);
if ((xids2 == null) || (xids2.length == 0)) {
trace("xares2.recover(XAResource.TMSTARTRSCAN): 0");
} else {
trace("xares2.recover(XAResource.TMSTARTRSCAN): " + xids2.length);
}
trace("con1 = xacon1.getConnection()");
conn1 = xaConn1.getConnection();
trace("stmt1 = con1.createStatement()");
stat1 = conn1.createStatement();
trace("con2 = xacon2.getConnection()");
conn2 = xaConn2.getConnection();
trace("stmt2 = con2.createStatement()");
stat2 = conn2.createStatement();
if (useOneDatabase) {
trace("stmt1.executeUpdate(\"DROP TABLE xatest1\")");
try {
stat1.executeUpdate("DROP TABLE xatest1");
} catch (SQLException e) {
// ignore
}
trace("stmt2.executeUpdate(\"DROP TABLE xatest2\")");
try {
stat2.executeUpdate("DROP TABLE xatest2");
} catch (SQLException e) {
// ignore
}
} else {
trace("stmt1.executeUpdate(\"DROP TABLE xatest\")");
try {
stat1.executeUpdate("DROP TABLE xatest");
} catch (SQLException e) {
// ignore
}
trace("stmt2.executeUpdate(\"DROP TABLE xatest\")");
try {
stat2.executeUpdate("DROP TABLE xatest");
} catch (SQLException e) {
// ignore
}
}
if (useOneDatabase) {
trace("stmt1.executeUpdate(\"CREATE TABLE xatest1 " +
"(id INT PRIMARY KEY, value INT)\")");
stat1.executeUpdate("CREATE TABLE xatest1 " +
"(id INT PRIMARY KEY, value INT)");
trace("stmt2.executeUpdate(\"CREATE TABLE xatest2 " +
"(id INT PRIMARY KEY, value INT)\")");
stat2.executeUpdate("CREATE TABLE xatest2 " +
"(id INT PRIMARY KEY, value INT)");
} else {
trace("stmt1.executeUpdate(\"CREATE TABLE xatest " +
"(id INT PRIMARY KEY, value INT)\")");
stat1.executeUpdate("CREATE TABLE xatest " +
"(id INT PRIMARY KEY, value INT)");
trace("stmt2.executeUpdate(\"CREATE TABLE xatest " +
"(id INT PRIMARY KEY, value INT)\")");
stat2.executeUpdate("CREATE TABLE xatest " +
"(id INT PRIMARY KEY, value INT)");
}
if (useOneDatabase) {
trace("stmt1.executeUpdate(\"INSERT INTO xatest1 " +
"VALUES (1, 0)\")");
stat1.executeUpdate("INSERT INTO xatest1 VALUES (1, 0)");
trace("stmt2.executeUpdate(\"INSERT INTO xatest2 " +
"VALUES (2, 0)\")");
stat2.executeUpdate("INSERT INTO xatest2 " +
"VALUES (2, 0)");
} else {
trace("stmt1.executeUpdate(\"INSERT INTO xatest " +
"VALUES (1, 0)\")");
stat1.executeUpdate("INSERT INTO xatest " +
"VALUES (1, 0)");
trace("stmt2.executeUpdate(\"INSERT INTO xatest " +
"VALUES (2, 0)\")");
stat2.executeUpdate("INSERT INTO xatest " +
"VALUES (2, 0)");
}
Xid xid1 = null;
Xid xid2 = null;
if (useOneDatabase) {
xid1 = SimpleXid.createRandom();
xid2 = SimpleXid.createRandom();
} else {
xid1 = SimpleXid.createRandom();
xid2 = xid1;
}
if (useOneDatabase) {
trace("xares1.start(xid1, XAResource.TMNOFLAGS)");
xares1.start(xid1, XAResource.TMNOFLAGS);
trace("xares2.start(xid2, XAResource.TMJOIN)");
xares2.start(xid2, XAResource.TMJOIN);
} else {
trace("xares1.start(xid1, XAResource.TMNOFLAGS)");
xares1.start(xid1, XAResource.TMNOFLAGS);
trace("xares2.start(xid2, XAResource.TMNOFLAGS)");
xares2.start(xid2, XAResource.TMNOFLAGS);
}
if (useOneDatabase) {
trace("stmt1.executeUpdate(\"UPDATE xatest1 " +
"SET value=1 WHERE id=1\")");
stat1.executeUpdate("UPDATE xatest1 " +
"SET value=1 WHERE id=1");
trace("stmt2.executeUpdate(\"UPDATE xatest2 " +
"SET value=1 WHERE id=2\")");
stat2.executeUpdate("UPDATE xatest2 " +
"SET value=1 WHERE id=2");
} else {
trace("stmt1.executeUpdate(\"UPDATE xatest " +
"SET value=1 WHERE id=1\")");
stat1.executeUpdate("UPDATE xatest " +
"SET value=1 WHERE id=1");
trace("stmt2.executeUpdate(\"UPDATE xatest " +
"SET value=1 WHERE id=2\")");
stat2.executeUpdate("UPDATE xatest " +
"SET value=1 WHERE id=2");
}
trace("xares1.end(xid1, XAResource.TMSUCCESS)");
xares1.end(xid1, XAResource.TMSUCCESS);
trace("xares2.end(xid2, XAResource.TMSUCCESS)");
xares2.end(xid2, XAResource.TMSUCCESS);
int ret1;
int ret2;
trace("ret1 = xares1.prepare(xid1)");
ret1 = xares1.prepare(xid1);
trace("xares1.prepare(xid1): " + ret1);
trace("ret2 = xares2.prepare(xid2)");
ret2 = xares2.prepare(xid2);
trace("xares2.prepare(xid2): " + ret2);
if ((ret1 != XAResource.XA_OK) && (ret1 != XAResource.XA_RDONLY)) {
throw new IllegalStateException(
"xares1.prepare(xid1) must return XA_OK or XA_RDONLY");
}
if ((ret2 != XAResource.XA_OK) && (ret2 != XAResource.XA_RDONLY)) {
throw new IllegalStateException(
"xares2.prepare(xid2) must return XA_OK or XA_RDONLY");
}
if (ret1 == XAResource.XA_OK) {
trace("xares1.commit(xid1, false)");
xares1.commit(xid1, false);
}
if (ret2 == XAResource.XA_OK) {
trace("xares2.commit(xid2, false)");
xares2.commit(xid2, false);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtils.closeSilently(stat1);
JdbcUtils.closeSilently(stat2);
JdbcUtils.closeSilently(conn1);
JdbcUtils.closeSilently(conn2);
if (xaConn1 != null) {
xaConn1.close();
}
if (xaConn2 != null) {
xaConn2.close();
}
}
}
private XADataSource createXADatasource(boolean useOneDatabase, String url) {
JdbcDataSource ds = new JdbcDataSource();
ds.setPassword(getPassword(""));
ds.setUser("sa");
if (useOneDatabase) {
ds.setURL(getURL("xa", true));
} else {
ds.setURL(url);
}
return ds;
}
}